package service

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"lktx-dns/db"
	"lktx-dns/entity"
	"math"
	"math/big"
	"net"
	"time"
)

type CaService struct {
}

type issuer struct {
	key  crypto.Signer
	cert *x509.Certificate
}

func sign(iss *issuer, domains []string, ipAddresses []string, alg x509.PublicKeyAlgorithm) (*x509.Certificate, string, string, error) {
	var cn string
	if len(domains) > 0 {
		cn = domains[0]
	} else if len(ipAddresses) > 0 {
		cn = ipAddresses[0]
	} else {
		return nil, "", "", fmt.Errorf("必须至少指定一个域名或 IP 地址")
	}
	key, keyStr, err := makeKey(alg)
	if err != nil {
		return nil, "", "", err
	}
	parsedIPs, err := parseIPs(ipAddresses)
	if err != nil {
		return nil, "", "", err
	}
	serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
	if err != nil {
		return nil, "", "", err
	}
	template := &x509.Certificate{
		DNSNames:    domains,
		IPAddresses: parsedIPs,
		Subject: pkix.Name{
			CommonName: cn,
		},
		SerialNumber: serial,
		NotBefore:    time.Now(),
		// Set the validity period to 2 years and 30 days, to satisfy the iOS and
		// macOS requirements that all server certificates must have validity
		// shorter than 825 days:
		// https://derflounder.wordpress.com/2019/06/06/new-tls-security-requirements-for-ios-13-and-macos-catalina-10-15/
		NotAfter: time.Now().AddDate(10, 0, 0),

		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  false,
	}
	der, err := x509.CreateCertificate(rand.Reader, template, iss.cert, key.Public(), iss.key)
	if err != nil {
		return nil, "", "", err
	}
	var buf bytes.Buffer
	err = pem.Encode(&buf, &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: der,
	})
	if err != nil {
		return nil, "", "", err
	}
	certificate, err := x509.ParseCertificate(der)
	return certificate, keyStr, buf.String(), err
}

func (receiver *CaService) GenSSL(name string) (string, string, error) {
	_, caIssuer := receiver.GetCa()
	_, err2 := parseIPs([]string{name})
	if err2 != nil {
		_, s, s2, err := sign(caIssuer, []string{name}, []string{}, x509.ECDSA)
		return s, s2, err
	} else {
		_, s, s2, err := sign(caIssuer, []string{}, []string{name}, x509.ECDSA)
		return s, s2, err
	}

}

// GetCa 获取Ca证书
func (receiver *CaService) GetCa() (*entity.CaInfoEntity, *issuer) {
	var results []*entity.CaInfoEntity
	db.DB.Model(&entity.CaInfoEntity{}).Find(&results)
	if len(results) > 0 {
		infoEntity := results[0]
		key, _ := readPrivateKey([]byte(*infoEntity.Key))
		content, _ := readCert([]byte(*infoEntity.Content))
		return results[0], &issuer{key, content}
	}
	key, content, err := makeIssuer(x509.ECDSA)
	if err != nil {
		return nil, nil
	}
	infoEntity := entity.CaInfoEntity{
		Key:     &key,
		Content: &content,
	}
	receiver.saveData(infoEntity)
	key1, _ := readPrivateKey([]byte(*infoEntity.Key))
	content2, _ := readCert([]byte(*infoEntity.Content))
	return &infoEntity, &issuer{key1, content2}
}

func (receiver *CaService) saveData(caInfo entity.CaInfoEntity) {
	if caInfo.Id == nil {
		db.DB.Save(&caInfo)
	} else {
		db.DB.Model(&entity.CaInfoEntity{}).Where("id = 1").Update("key", caInfo.Key).Update("content", caInfo.Content)
	}
}

func parseIPs(ipAddresses []string) ([]net.IP, error) {
	var parsed []net.IP
	for _, s := range ipAddresses {
		p := net.ParseIP(s)
		if p == nil {
			return nil, fmt.Errorf("invalid IP address %s", s)
		}
		parsed = append(parsed, p)
	}
	return parsed, nil
}

func makeIssuer(alg x509.PublicKeyAlgorithm) (string, string, error) {
	key, s, err := makeKey(alg)
	if err != nil {
		return "", "", err
	}
	_, s2, err := makeRootCert(key)
	if err != nil {
		return "", "", err
	}
	return s, s2, nil
}

func readPrivateKey(keyContents []byte) (crypto.Signer, error) {
	block, _ := pem.Decode(keyContents)
	if block == nil {
		return nil, fmt.Errorf("no PEM found")
	} else if block.Type == "PRIVATE KEY" {
		signer, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("failed to parse PKCS8: %w", err)
		}
		switch t := signer.(type) {
		case *rsa.PrivateKey:
			return signer.(*rsa.PrivateKey), nil
		case *ecdsa.PrivateKey:
			return signer.(*ecdsa.PrivateKey), nil
		default:
			return nil, fmt.Errorf("unsupported PKCS8 key type: %t", t)
		}
	} else if block.Type == "RSA PRIVATE KEY" {
		return x509.ParsePKCS1PrivateKey(block.Bytes)
	} else if block.Type == "EC PRIVATE KEY" || block.Type == "ECDSA PRIVATE KEY" {
		return x509.ParseECPrivateKey(block.Bytes)
	}
	return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
}

func readCert(certContents []byte) (*x509.Certificate, error) {
	block, _ := pem.Decode(certContents)
	if block == nil {
		return nil, fmt.Errorf("no PEM found")
	} else if block.Type != "CERTIFICATE" {
		return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
	}
	return x509.ParseCertificate(block.Bytes)
}

func calculateSKID(pubKey crypto.PublicKey) ([]byte, error) {
	spkiASN1, err := x509.MarshalPKIXPublicKey(pubKey)
	if err != nil {
		return nil, err
	}

	var spki struct {
		Algorithm        pkix.AlgorithmIdentifier
		SubjectPublicKey asn1.BitString
	}
	_, err = asn1.Unmarshal(spkiASN1, &spki)
	if err != nil {
		return nil, err
	}
	skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
	return skid[:], nil
}

func makeKey(alg x509.PublicKeyAlgorithm) (crypto.Signer, string, error) {
	var key crypto.Signer
	var err error
	switch {
	case alg == x509.RSA:
		key, err = rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			return nil, "", err
		}
	case alg == x509.ECDSA:
		key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
		if err != nil {
			return nil, "", err
		}
	}
	der, err := x509.MarshalPKCS8PrivateKey(key)
	if err != nil {
		return nil, "", err
	}
	var buf bytes.Buffer
	err = pem.Encode(&buf, &pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: der,
	})
	if err != nil {
		return nil, "", err
	}
	return key, buf.String(), nil
}

func makeRootCert(key crypto.Signer) (*x509.Certificate, string, error) {
	serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
	if err != nil {
		return nil, "", err
	}
	skid, err := calculateSKID(key.Public())
	if err != nil {
		return nil, "", err
	}
	template := &x509.Certificate{
		Subject: pkix.Name{
			CommonName: "lktx root ca " + hex.EncodeToString(serial.Bytes()[:3]),
		},
		SerialNumber: serial,
		NotBefore:    time.Now(),
		NotAfter:     time.Now().AddDate(100, 0, 0),

		SubjectKeyId:          skid,
		AuthorityKeyId:        skid,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  true,
		MaxPathLenZero:        true,
	}

	der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
	if err != nil {
		return nil, "", err
	}
	var buf bytes.Buffer
	err = pem.Encode(&buf, &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: der,
	})
	if err != nil {
		return nil, "", err
	}
	certificate, err := x509.ParseCertificate(der)
	return certificate, buf.String(), err
}
